package com.atguigu.dga.util;

import org.apache.hadoop.hive.ql.lib.DefaultGraphWalker;
import org.apache.hadoop.hive.ql.lib.Dispatcher;
import org.apache.hadoop.hive.ql.lib.GraphWalker;
import org.apache.hadoop.hive.ql.lib.Node;
import org.apache.hadoop.hive.ql.parse.*;

import java.util.Collections;
import java.util.Stack;

/**
 * Created by Smexy on 2023/10/31

 1.通过语法树，如何判断当前sql有没有 join|group by|union all
        只需要从树的根节点向下遍历，如果出现了子(孙，重孙..)节点的名字有 TOK_GROUPBY，说明这个sql一定是有group by
        只需要从树的根节点向下遍历，如果出现了子(孙，重孙..)节点的名字有 TOK_UNIONALL，说明这个sql一定是有union all
        只需要从树的根节点向下遍历，如果出现了子(孙，重孙..)节点的名字有 TOK_LEFTOUTJOIN，说明这个sql一定是有left join

 2.通过语法树，如何能知道这个sql查询了哪些表?
        遍历树，找TOK_TABNAME。
            如果是库名.表名(TOK_TABNAME 有两个子节点)，找第二个子节点。
            如果是表名(TOK_TABNAME 有一个子节点)，找第一个子节点。

 3.通过语法树，如何能知道这个sql的where部分过滤了哪些字段?
        遍历树，先找到 TOK_WHERE 节点，再向下遍历。
        a)找到TOK_WHERE，获取它的子节点
        b) 看子节点是不是比较运算符(=，<,>)
                是。遍历当前节点的子节点
                    判断是不是.
                        是.，获取.这个节点的第二个孩子
                        不是.，判断是不是TOK_TABLE_OR_COL,是的话取它的第一个孩子

                不是，基本上都是逻辑运算符(and or ),需要向下遍历，继续判断


 */
public class SqlParser
{
    public static void main(String[] args) throws Exception {

        String sql = "  with tmp1 as (" +
            "  select " +
            "  t1.name,t1.age,t2.score " +
            "  from student t1 left join score t2 " +
            "  on t1.id = t2.id " +
            ") " +
            "insert overwrite table result " +
            "select " +
            "   name,sum(score) totalScore " +
            "from tmp1 " +
            "group by name " +
            "union all " +
            "select " +
            "   age,sum(score) totalScore " +
            "from tmp1 " +
            " where age > 10 " +
            "group by age " ;

        String sql2 = " with t1 as (select aa(a),b,c,dt as dd from tt1,  tt2 \n" +
            "             where tt1.a=tt2.b and dt='2023-05-11'  )\n" +
            "  insert overwrite table tt9  \n" +
            "  select a,b,c \n" +
            "  from t1 \n" +
            "  where    dt = date_add('${xxx}',-4 )    \n" +
            "  union \n" +
            "  select a,b,c \n" +
            "  from t2\n" +
            "   where    dt = date_add('${xxx} ',-7 )  ";

        String sql3 = "select * from gmall.dim_user_zip t1  where  (dt = xxxx and a = b) or c > d";

        String sql4 = "select * from dim_user_zip t1  where t1.name = 'jack' ";

        String sql5 = "select * from dim_user_zip  where name = 'jack' ";

        parse(sql5,new MyDispatcher());

    }

    /*
            在语法树上，每个节点都有自己的类型，类型会使用 TOK_XXX来表示。
     */
    public static void parse(String sql, Dispatcher dispatcher) throws Exception {

        //创建一个解析器
        ParseDriver parseDriver = new ParseDriver();
        //使用解析器把 字符串的sql 解析为一个语法树
        ASTNode tree = parseDriver.parse(sql);
        //从query节点遍历
        ASTNode query = (ASTNode)tree.getChild(0);

        //hive提供了一个遍历树的组件，只需要编写找到一个节点后执行的判断逻辑即可，而遍历操作由组件自动完成
        GraphWalker ogw = new DefaultGraphWalker(dispatcher);
        // 用遍历器遍历整个语法树
        ogw.startWalking(Collections.singletonList(query), null);

    }

    //把找到一个节点后执行的业务逻辑编写在Dispatcher类中
    public static class MyDispatcher implements Dispatcher{

        /*
            会被GraphWalker在遍历每一个节点时都执行.
                Node nd就代表当前节点，从这个对象中获取非常丰富的信息。
                    示例：
                        name:24,type:24,text:dim_user_zip
                        name:1061,type:1061,text:TOK_TABNAME
                        name:1062,type:1062,text:TOK_TABREF
                    节点的name,type都是一个整数，提前在 HiveParser中定义好了。
                    节点的text，是我们要获取的信息。

                    name可以直接从Node获取，但是type和text必须强转为ASTNode才能获取
         */
        @Override
        public Object dispatch(Node nd, Stack<Node> stack, Object... nodeOutputs) throws SemanticException {
            ASTNode astNode = (ASTNode) nd;
            System.out.println("name:"+nd.getName()+",type:"+astNode.getType()+",text:"+astNode.getText());
            return null;
        }
    }

}
